﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Windows.Forms;
using System.Configuration.Provider;
using System.Globalization;
using Medianamik.Core.Cache;
using Medianamik.Core.Interfaces;
using Medianamik.Core.Logging;


namespace Medianamik.Core.Security
{
    /// <summary>
    /// Gestion des rôles dans les noeuds de Médianamik
    /// </summary>
    public class MedianamikRoleProvider : RoleProvider
    {
        #region Propriétés

        private string _applicationName;

        internal RoleCacheManager RoleCache
        {
            get
            {
                return RoleCacheManager.Instance;
            }
        }

        private static ILogger _logger;
        protected static ILogger Logger
        {
            get
            {
                return _logger
                    ?? (_logger = new ObjectFactory<ILogger>().Get("MedianamikRoleProviderLogger"));
            }
        }

        private List<IRole> RoleList
        {
            get
            {
                Func<List<IRole>> func = () => DALProviderManager.DALProvider.SecurityDALProvider.GetRoles().ToList();
                return RoleCache.Get("GetAllRoles", func);
            }
        }

        private IEnumerable<Group> GroupList
        {
            get
            {
                return SecurityManager.AllGroups;
            }
        }

        private List<IRoleInGroup> RoleInGroupList
        {
            get
            {
                Func<List<IRoleInGroup>> func = () => DALProviderManager.DALProvider.SecurityDALProvider.GetRolesInGroups().ToList();
                return RoleCache.Get("GetRolesInGroups", func);
            }
        }

        //private List<IUserInGroup> UserInGroupList
        //{
        //    get
        //    {
        //        return RoleCache.Get("GetUsersInGroups",
        //            delegate
        //            {
        //                return DALProviderManager.DALProvider.
        //                      SecurityDALProvider.GetUsersInGroups().ToList();
        //            });
        //    }
        //}

        private List<IUserInGroup> UserInGroupList
        {
            get
            {
                Func<List<IUserInGroup>> func = () => DALProviderManager.DALProvider.SecurityDALProvider.GetUsersInGroups().ToList();
                return RoleCache.Get("GetUsersInGroups", func);
            }
        }


        #endregion

        #region Méthodes

        #region Privées

        /// <summary>
        /// Fonction utilitaire pour retourner une valueur du fichier de configuration
        /// </summary>
        /// <param name="configValue"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        private string GetConfigValue(string configValue, string defaultValue)
        {
            if (String.IsNullOrEmpty(configValue))
                return defaultValue.ToString();

            return configValue.ToString();
        }

        #endregion

        #endregion

        #region Overrides

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);

            _applicationName = GetConfigValue(config["applicationName"],
                                              System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            RoleCache.Clear();
        }

        #endregion

        #region Abstract RoleProvider

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException("Add users to groups instead");
        }

        public void AddUsersToGroups(string[] usernames, string[] groupNames)
        {
            if (groupNames.Any(g => g.ToLowerInvariant() == MedianamikGroups.LoweredSuperAdminGroupName))
            {
                throw new CoreException("Cannot add users to SuperAdmins group");
            }
            DALProviderManager.DALProvider.SecurityDALProvider.AddUsersToGroups(usernames, groupNames);
        }

        public void AddRolesToGroups(string[] roleNames, string[] groupNames)
        {
            DALProviderManager.DALProvider.SecurityDALProvider.AddRolesToGroups(roleNames, groupNames);
        }

        public override string ApplicationName
        {
            get { return _applicationName; }
            set { _applicationName = value; }
        }

        public override void CreateRole(string roleName)
        {
            if (roleName == null || roleName == string.Empty)
                throw new ProviderException("Role name cannot be empty or null.");
            if (roleName.Contains(","))
                throw new ArgumentException("Role names cannot contain commas.");
            if (RoleList.Any(r => string.Equals(roleName, r.RoleName)))
                throw new ProviderException("Role name already exists.");
            if (roleName.Length > 255)
                throw new ProviderException("Role name cannot exceed 255 characters.");

            DALProviderManager.DALProvider.SecurityDALProvider.CreateRole(roleName);

        }

        public void CreateGroup(string groupName, Guid parentGroupId)
        {
            if (string.IsNullOrEmpty(groupName))
                throw new ProviderException("Group name cannot be empty or null.");
            if (groupName.Contains(","))
                throw new ArgumentException("Group names cannot contain commas.");
            if (GroupList.Any(r => string.Equals(groupName, r.Name)))
                throw new ProviderException("Group name already exists.");
            if (groupName.Length > 255)
                throw new ProviderException("Group name cannot exceed 255 characters.");
            if (parentGroupId.Equals(Guid.Empty))
                throw new ArgumentException("ParentGroupId cannot be Guid.Empty.");

            DALProviderManager.DALProvider.SecurityDALProvider.CreateGroup(groupName, parentGroupId);
            SecurityManager.ReloadProviders();
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            if (throwOnPopulatedRole && GetUsersInRole(roleName).Length > 0)
            {
                throw new ProviderException("Cannot delete a populated role.");
            }

            try
            {
                DALProviderManager.DALProvider.SecurityDALProvider.DeleteRole(roleName);

                return true;
            }
            catch (Exception e)
            {
                Logger.Warn("Error while deleting role", e);
                return false;
            }
            finally
            {
                RoleCache.Clear();
            }

        }

        public bool DeleteGroup(string groupName, bool throwOnPopulatedGroup)
        {
            if (throwOnPopulatedGroup && GetUsersInGroup(groupName).Length > 0)
            {
                throw new ProviderException("Cannot delete a populated  group.");
            }
            try
            {
                DALProviderManager.DALProvider.SecurityDALProvider.DeleteGroup(groupName);
                return true;
            }
            finally
            {
                RoleCache.Clear();
                SecurityManager.ReloadProviders();
            }
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            string loweredUsernameToMatch = usernameToMatch.ToLowerInvariant();
            string loweredRolenameToMatch = roleName.ToLowerInvariant();
            var users = from userInGroup in UserInGroupList
                        let loweredUsername = userInGroup.UserName.ToLowerInvariant()
                        join roleInGroup in RoleInGroupList on userInGroup.GroupId equals roleInGroup.GroupId
                        let loweredRolename = roleInGroup.RoleName.ToLowerInvariant()
                        where loweredUsernameToMatch == loweredUsername
                            && loweredRolenameToMatch == loweredRolename
                        select userInGroup.UserName;

            return users.ToArray();

        }
        public string[] FindUsersInGroup(string groupName, string usernameToMatch)
        {
            string loweredUsernameToMatch = usernameToMatch.ToLowerInvariant();
            string loweredGrounameToMatch = groupName.ToLowerInvariant();
            var users = from userInGroup in UserInGroupList
                        let loweredUsername = userInGroup.UserName.ToLowerInvariant()
                        let loweredGroupname = userInGroup.UserName.ToLowerInvariant()
                        where loweredUsernameToMatch == loweredUsername
                            && loweredGrounameToMatch == loweredGroupname
                        select userInGroup.UserName;

            return users.ToArray();
        }

        public override string[] GetAllRoles()
        {
            return RoleList.Select(r => r.RoleName).ToArray();
        }

        public IRole[] GetAllIRoles()
        {
            return RoleList.ToArray();
        }

        public string[] GetAllGroups()
        {
            return GroupList.Select(g => g.Name).ToArray();
        }

        public Group[] GetAllIGroups()
        {
            return GroupList.ToArray();
        }

        public override string[] GetRolesForUser(string username)
        {
            if (this.IsUserInGroup(username, MedianamikGroups.LoweredSuperAdminGroupName))
            {
                //Le group SA doit être associé à tous les roles.
                return GetAllRoles();
            }

            var loweredUsername = username.ToLowerInvariant();
            var roles = from userInGroup in UserInGroupList
                        join roleInGroup in RoleInGroupList on userInGroup.GroupId equals roleInGroup.GroupId
                        where string.Equals(userInGroup.UserName.ToLowerInvariant(), loweredUsername)
                        select roleInGroup.RoleName;

            return roles.ToArray();
        }

        public string[] GetGroupsForUser(string username)
        {
            var loweredUsername = username.ToLowerInvariant();
            var groups = from userInGroup in UserInGroupList
                         where string.Equals(userInGroup.UserName.ToLowerInvariant(), loweredUsername)
                         select userInGroup.GroupName;

            return groups.ToArray();
        }

        public override string[] GetUsersInRole(string roleName)
        {
            var loweredRolename = roleName.ToLowerInvariant();
            var users = from roleInGroup in RoleInGroupList
                        join userInGroup in UserInGroupList on roleInGroup.GroupId equals userInGroup.GroupId
                        where string.Equals(roleInGroup.RoleName.ToLowerInvariant(), loweredRolename)
                        select userInGroup.UserName;

            return users.ToArray();

        }
        public string[] GetUsersInGroup(string groupName)
        {
            var loweredGroupname = groupName.ToLowerInvariant();
            var users = from userInGroup in UserInGroupList
                        where string.Equals(userInGroup.GroupName.ToLowerInvariant(), loweredGroupname)
                        select userInGroup.UserName;

            return users.ToArray();

        }

        public string[] GetRolesInGroup(string groupName)
        {
            var loweredGroupname = groupName.ToLowerInvariant();
            var roles = from roleInGroup in RoleInGroupList
                        where string.Equals(roleInGroup.GroupName.ToLowerInvariant(), loweredGroupname)
                        select roleInGroup.RoleName;

            return roles.ToArray();
        }

        public string[] GetGroupsInRole(string pRoleName)
        {
            var loweredRolename = pRoleName.ToLowerInvariant();
            var groups = from roleInGroup in RoleInGroupList
                         where string.Equals(roleInGroup.RoleName.ToLowerInvariant(), loweredRolename)
                         select roleInGroup.GroupName;

            return groups.ToArray();

        }

        public override bool IsUserInRole(string username, string roleName)
        {
            return RoleCache.Get("IsUserInRole" + username.ToLowerInvariant() + roleName.ToLowerInvariant()
                , () =>
                    {
                        return GetRolesForUser(username).Any(r => string.Equals(r, roleName, StringComparison.InvariantCultureIgnoreCase));
                    });
        }
        public bool IsUserInGroup(string username, string groupName)
        {
            return RoleCache.Get("IsUserInGroup" + username.ToLowerInvariant() + groupName.ToLowerInvariant()
                , () =>
                    {
                        return GetGroupsForUser(username).Any(g => string.Equals(g, groupName, StringComparison.InvariantCultureIgnoreCase));
                    });
        }

        public bool UserHasRight(string username, Guid nodeId, SecurityLevels level)
        {
            return UserHasRight(username, NodeManager.GetNode(nodeId), level);
        }

        //TODO : Optimiser - Garder les groupes en cache - ici on a besoin des IDs des groupes
        //Il y a déjà de la cache sur les noms des groupes dans le MedianamikRoleProvider (à voir)
        public bool UserHasRight(string username, INode pNode, SecurityLevels level)
        {
            // Lorsque le noeud n'existe pas, il est virtuel. On y a donc droit.
            if (pNode == null)
                return true;

            var key = "UserHasRightByNode" + username.ToLowerInvariant() + pNode.ID + (int)level;

            return RoleCache.Get(key,
                () =>
                {
                    var userGroups = UserInGroupList.Where(uig => string.Equals(uig.UserName, username, StringComparison.InvariantCultureIgnoreCase))
                        .ToArray();

                    var isSA = userGroups.Any(ug => ug.GroupId == MedianamikGroups.SuperAdmins);

                    if (isSA)
                        return true;

                    var permissions = new List<Permission>();
                    
                    //Vérifier les restrictions sur le type (plus restrictif que le noeud et en mémoire donc plus rapide)
                    if (!AnyOfGroupHasRight(userGroups, pNode.NodeType, level))
                        return false;

                    //Vérifier les restrictions sur le noeud
                    foreach (var group in userGroups)
                    {
                        var permission = pNode.Permissions
                            .SingleOrDefault(p => p.GroupId.Equals(group.GroupId)
                                && p.SecuredObjectId.Equals(pNode.ID));

                        if (permission == null)
                        {
                            permission = pNode.InheritedPermissions.SingleOrDefault(p => p.GroupId.Equals(group.GroupId) && p.SecuredObjectId.Equals(pNode.ID));
                            if (permission != null)
                            {
                                permissions.Add(permission);
                            }
                            else
                            {
                                // Si on a un droit hérité qui est à "Attribuer" (Donc tout les droits), on retourne true
                                return true;
                            }
                        }
                        else
                            permissions.Add(permission);
                    }

                    return permissions.Count <= 0 || !permissions.All(p => p.SecurityLevel < level);
                });
        }

        public bool UserHasRight(string username, NodeType type, SecurityLevels level)
        {
            string key = "UserHasRightByNodeType" + username.ToLowerInvariant() + type.ID + (int)level;
            return RoleCache.Get(key,
                () =>
                {
                    var userGroups = UserInGroupList.Where(uig => string.Equals(uig.UserName, username, StringComparison.InvariantCultureIgnoreCase))
                       .ToArray();
                    return AnyOfGroupHasRight(userGroups, type, level);
                });
        }

        private bool AnyOfGroupHasRight(IGroup[] groups, NodeType type, SecurityLevels level)
        {
            if (groups.Any(g => g.GroupId.Equals(MedianamikGroups.SuperAdmins)))
                return true;

            List<Permission> permissions = new List<Permission>();

            foreach (IGroup group in groups)
            {
                Permission permission = type.Permissions.SingleOrDefault(p => p.GroupId.Equals(group.GroupId) && p.SecuredObjectId.Equals(type.ID));
                if (permission == null)
                {
                    permission = type.InheritedPermissions.SingleOrDefault(p => p.GroupId.Equals(group.GroupId) && p.SecuredObjectId.Equals(type.ID));
                    if (permission != null)
                        permissions.Add(permission);
                }
                else
                    permissions.Add(permission);
            }

            if (permissions.Count > 0 && permissions.All(p => p.SecurityLevel < level))
                return false;

            return true;
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            throw new NotImplementedException("Remove users from groups instead");
        }
        public void RemoveUsersFromGroups(string[] usernames, string[] groupNames)
        {
            DALProviderManager.DALProvider.SecurityDALProvider.RemoveUsersFromGroups(usernames, groupNames);

            RoleCache.Clear();
        }

        public void RemoveRolesFromGroups(string[] rolenames, string[] groupNames)
        {
            DALProviderManager.DALProvider.SecurityDALProvider.RemoveRolesFromGroups(rolenames, groupNames);
            RoleCache.Clear();
        }

        public override bool RoleExists(string roleName)
        {
            return RoleList.Any(r => string.Equals(r.RoleName, roleName, StringComparison.InvariantCultureIgnoreCase));
        }
        public bool GroupExists(string groupName)
        {
            return GroupList.Any(g => string.Equals(g.Name, groupName, StringComparison.InvariantCultureIgnoreCase));
        }

        #endregion

        #region Helper Methods



        #endregion
    }
}
